package com.immediatus.engine.layer;

import android.util.SparseArray;
import com.immediatus.engine.Unit;
import com.immediatus.engine.contracts.ITouchArea;
import com.immediatus.engine.contracts.IUnit;
import com.immediatus.engine.input.TouchEvent;
import com.immediatus.engine.util.RunnableHandler;
import com.immediatus.graphics.camera.Camera;
import com.immediatus.graphics.util.OpenGLWrapper;
import com.immediatus.util.SmartList;

import javax.microedition.khronos.opengles.GL10;
import java.util.ArrayList;

import static com.immediatus.engine.contracts.ITouchArea.ITouchAreaMatcher;


public class Canvas extends Unit {
    private static final int TOUCHAREAS_CAPACITY_DEFAULT = 4;
    private static final int VERTEX_INDEX_X = 0;
    private static final int VERTEX_INDEX_Y = 1;

    private Canvas _parent;
    private Canvas _child;
    private SmartList<ITouchArea> _touchAreas = new SmartList<ITouchArea>(TOUCHAREAS_CAPACITY_DEFAULT);

    private float _secondsElapsedTotal;
    private boolean _childModalDraw;
    private boolean _childModalUpdate;
    private boolean _childModalTouch;
    private final RunnableHandler _handler = new RunnableHandler();
    private IOnCanvasTouchListener _onTouchListener;
    private IOnAreaTouchListener _onAreaTouchListener;
    private boolean _onAreaTouchTraversalBackToFront = true;
    private boolean _touchAreaBindingEnabled = false;
    private final SparseArray<ITouchArea> _touchAreaBindings = new SparseArray<ITouchArea>();
    private boolean _onTouchListenerBindingEnabled = false;
    private final SparseArray<IOnCanvasTouchListener> _onTouchListenerBindings = new SparseArray<IOnCanvasTouchListener>();

    public Canvas(){
    }

    public float getSecondsElapsedTotal(){
        return this._secondsElapsedTotal;
    }

    public void setOnTouchListener(final IOnCanvasTouchListener touchListener_){
        this._onTouchListener = touchListener_;
    }

    public IOnCanvasTouchListener getOnTouchListener(){
        return this._onTouchListener;
    }

    public boolean hasOnLayerTouchListener(){
        return this._onTouchListener != null;
    }

    public void setOnAreaTouchListener(final IOnAreaTouchListener touchListener_){
        this._onAreaTouchListener = touchListener_;
    }

    public IOnAreaTouchListener getOnAreaTouchListener(){
        return this._onAreaTouchListener;
    }

    public boolean hasOnAreaTouchListener(){
        return this._onAreaTouchListener != null;
    }

    private void setParent(final Canvas parent_){
        this._parent = parent_;
    }

    public boolean hasChild(){
        return this._child != null;
    }

    public Canvas getChild(){
        return this._child;
    }

    public void setChildModal(final Canvas value_){
        this.setChild(value_, true, true, true);
    }

    public void setChild(final Canvas value_){
        this.setChild(value_, false, false, false);
    }

    public void setChild(final Canvas childCanvas_, final boolean modalDraw_, final boolean modalUpdate_, final boolean modalTouch_){
        childCanvas_.setParent(this);
        this._child = childCanvas_;
        this._childModalDraw = modalDraw_;
        this._childModalUpdate = modalUpdate_;
        this._childModalTouch = modalTouch_;
    }

    public void clearChild(){
        this._child = null;
    }

    public void setOnAreaTouchTraversalBackToFront(){
        this._onAreaTouchTraversalBackToFront = true;
    }

    public void setOnAreaTouchTraversalFrontToBack(){
        this._onAreaTouchTraversalBackToFront = false;
    }

    public boolean isTouchAreaBindingEnabled(){
        return this._touchAreaBindingEnabled;
    }

    public void setTouchAreaBindingEnabled(final boolean value_){
        if (this._touchAreaBindingEnabled && !value_){
            this._touchAreaBindings.clear();
        }
        this._touchAreaBindingEnabled = value_;
    }

    public boolean isOnTouchListenerBindingEnabled(){
        return this._onTouchListenerBindingEnabled;
    }

    public void setOnTouchListenerBindingEnabled(final boolean value_){
        if (this._onTouchListenerBindingEnabled && !value_){
            this._onTouchListenerBindings.clear();
        }
        this._onTouchListenerBindingEnabled = value_;
    }

    @Override
    protected void onManagedDraw(final GL10 gl_, final Camera camera_){
        final Canvas childCanvas = this._child;

        if (childCanvas == null || !this._childModalDraw){
            camera_.onApplyLayerMatrix(gl_);
            OpenGLWrapper.setModelViewIdentityMatrix(gl_);
            super.onManagedDraw(gl_, camera_);
        }

        if (childCanvas != null){
            childCanvas.onDraw(gl_, camera_);
        }
    }

    @Override
    protected void onManagedUpdate(final float secondsElapsed_){
        this._secondsElapsedTotal += secondsElapsed_;

        this._handler.onUpdate(secondsElapsed_);

        final Canvas childCanvas = this._child;
        if (childCanvas == null || !this._childModalUpdate){
            super.onManagedUpdate(secondsElapsed_);
        }

        if (childCanvas != null){
            childCanvas.onUpdate(secondsElapsed_);
        }
    }

    public boolean onTouchEvent(final TouchEvent event_){
        final int action = event_.getAction();
        final boolean isActionDown = event_.isActionDown();

        if (!isActionDown){
            if (this._onTouchListenerBindingEnabled){
                final IOnCanvasTouchListener boundOnLayerTouchListener = this._onTouchListenerBindings.get(event_.getPointerID());
                if (boundOnLayerTouchListener != null){
                    switch (action){
                        case TouchEvent.ACTION_UP:
                        case TouchEvent.ACTION_CANCEL:
                            this._onTouchListenerBindings.remove(event_.getPointerID());
                    }
                    final Boolean handled = this._onTouchListener.onTouchEvent(this, event_);
                    if (handled != null && handled){
                        return true;
                    }
                }
            }
            if (this._touchAreaBindingEnabled){
                final SparseArray<ITouchArea> touchAreaBindings = this._touchAreaBindings;
                final ITouchArea boundTouchArea = touchAreaBindings.get(event_.getPointerID());
                if (boundTouchArea != null){
                    final float x = event_.getX();
                    final float y = event_.getY();

                    switch (action){
                        case TouchEvent.ACTION_UP:
                        case TouchEvent.ACTION_CANCEL:
                            touchAreaBindings.remove(event_.getPointerID());
                    }
                    final Boolean handled = this.onAreaTouchEvent(event_, x, y, boundTouchArea);
                    if (handled != null && handled){
                        return true;
                    }
                }
            }
        }

        final Canvas childCanvas = this._child;
        if (childCanvas != null){
            final boolean handledByChild = this.onChildTouchEvent(event_);
            if (handledByChild){
                return true;
            } else if (this._childModalTouch){
                return false;
            }
        }

        final float x = event_.getX();
        final float y = event_.getY();

        final ArrayList<ITouchArea> touchAreas = this._touchAreas;
        if (touchAreas != null){
            final int touchAreaCount = touchAreas.size();
            if (touchAreaCount > 0){
                if (this._onAreaTouchTraversalBackToFront){
                    for (final ITouchArea touchArea : touchAreas){
                        if (touchArea.contains(x, y)){
                            final Boolean handled = this.onAreaTouchEvent(event_, x, y, touchArea);
                            if (handled != null && handled){
                                if (this._touchAreaBindingEnabled && isActionDown){
                                    this._touchAreaBindings.put(event_.getPointerID(), touchArea);
                                }
                                return true;
                            }
                        }
                    }
                } else{
                    for (int i = touchAreaCount - 1; i >= 0; i--){
                        final ITouchArea touchArea = touchAreas.get(i);
                        if (touchArea.contains(x, y)){
                            final Boolean handled = this.onAreaTouchEvent(event_, x, y, touchArea);
                            if (handled != null && handled){
                                if (this._touchAreaBindingEnabled && isActionDown){
                                    this._touchAreaBindings.put(event_.getPointerID(), touchArea);
                                }
                                return true;
                            }
                        }
                    }
                }
            }
        }
        if (this._onTouchListener != null){
            final Boolean handled = this._onTouchListener.onTouchEvent(this, event_);
            if (handled != null && handled){
                if (this._onTouchListenerBindingEnabled && isActionDown){
                    this._onTouchListenerBindings.put(event_.getPointerID(), this._onTouchListener);
                }
                return true;
            } else{
                return false;
            }
        } else{
            return false;
        }
    }

    private Boolean onAreaTouchEvent(final TouchEvent event_, final float eventX_, final float eventY_, final ITouchArea touchArea_){
        final float[] touchAreaLocalCoordinates = touchArea_.convertLayerToLocalCoordinates(eventX_, eventY_);
        final float touchAreaLocalX = touchAreaLocalCoordinates[VERTEX_INDEX_X];
        final float touchAreaLocalY = touchAreaLocalCoordinates[VERTEX_INDEX_Y];

        if(!touchArea_.isVisible()) return null;

        final boolean handledSelf = touchArea_.onAreaTouched(event_, touchAreaLocalX, touchAreaLocalY);
        if (handledSelf){
            return Boolean.TRUE;
        } else if (this._onAreaTouchListener != null){
            return this._onAreaTouchListener.onAreaTouched(event_, touchArea_, touchAreaLocalX, touchAreaLocalY);
        } else{
            return null;
        }
    }

    protected boolean onChildTouchEvent(final TouchEvent event_){
        return this._child.onTouchEvent(event_);
    }

    @Override
    public void reset(){
        super.reset();

        this.clearChild();
    }

    @Override
    public void setParent(final IUnit unit_){
    }

    public void postRunnable(final Runnable runnable_){
        this._handler.postRunnable(runnable_);
    }

    public void registerTouchArea(final ITouchArea touchArea_){
        this._touchAreas.add(touchArea_);
    }

    public boolean unregisterTouchArea(final ITouchArea touchArea_){
        return this._touchAreas.remove(touchArea_);
    }

    public boolean unregisterTouchAreas(final ITouchAreaMatcher matcher_){
        return this._touchAreas.removeAll(matcher_);
    }

    public void clearTouchAreas(){
        this._touchAreas.clear();
    }

    public ArrayList<ITouchArea> getTouchAreas(){
        return this._touchAreas;
    }

    public void back(){
        this.clearChild();

        if (this._parent != null){
            this._parent.clearChild();
            this._parent = null;
        }
    }

    public static interface IOnAreaTouchListener{
        public boolean onAreaTouched(final TouchEvent event_, final ITouchArea touchArea_, final float x_, final float y_);
    }

    public static interface IOnCanvasTouchListener{
        public boolean onTouchEvent(final Canvas canvas_, final TouchEvent event_);
    }
}
